Water quality parameters retrieval using Remote Sensing data - Part 01

The use of remote sensing data to predict water quality variables that are optically active (i.e., interacts with light) has been applied to ocean and coastal waters for ~50 years. Thanks to the new generation of sensors with adequate spectral, radiometric and spatial resolution (i.e., Landsat, Sentinel-2, etc) in the last 15 years the community started to use RS to freshwater studies.

Remote sensing allow us to predict some WQ parameters: Suspended sediments, chlorophyl-a, phycocianin, dissolved organic matter, carbon, secchi disk depth, turbidity…It is an important source of data that could help biologists, limnologists, and all the aquatic science community into understanding of water pattern.

In this Notebook, we gonna learn how to use Remote Sensing data to predict water quality variables (Chl-a and Suspended Sediments). For that, we gonna use a hyperspectral dataset for global freshwater (GLORIA) to simulated Landsat and Sentinel-2 bands (i.e., makes the in situ data the “same” as satellite see). After that, we gonna do some exploratory analysis and filters to remove outliers and develop a empirical and Machine Learning algorithm (Random Forest) to predict Chl-a and TSS for satellite images.

The processing flow is divided into three topics:

  1. Installing the packages, downloading the data, simulate the bands and removing outliers (pre-processing step)
  2. Model development (train, validation, and full model development)
  3. Model application: application of the algorithms to satellite data using STAC and Google Earth Engine

#install.packages(c('data.table',
#                        'dplyr',
#                        'rstac',
#                        'terra',
#                        'raster',
#                        'mapview',
#                        'Metrics',
#                        'geodrawr',
#                        'svDialogs',
#                         'PerformanceAnalytics',
#                        'rstac',
#                        'wesanderson'))


require(data.table)
require(dplyr)
require(rstac)
require(terra)
require(mapview)
require(httr)
require(Metrics)
require(geodrawr)
require(svDialogs)
require(rstac)
require(wesanderson)
require(PerformanceAnalytics)

Downloading GLORIA data

The GLORIA dataset is a compilation of remote sensing and water quality data for global waters, with dedicated data for freshwater ecosystems. It is free and available for everyone, and covers most part of the globe with more than 7,000 samples (Figure 01)

Figure 01

For more information, users are refered to the publication (Lehmann et al. 2023), the dataset in PANGAEA and the Nature Earth and Environmment blog post

In the following codes, we are gonna:

  1. Create the necessary folders
  2. Download the data from PANGAEA
  3. Extract it in R

# Let's download the GLORIA to a empty folder called "Data" in our project

## First, create directories

dir.create('Data')
Warning: 'Data' already exists
dir.create("Outputs")
Warning: 'Outputs' already exists
dir.create("Scripts")
Warning: 'Scripts' already exists
## URL
URL = 'https://download.pangaea.de/dataset/948492/files/GLORIA-2022.zip'

# Before download, let`s set timeout to 200s (sometimes PANGAEA download is slow)

options(timeout=300)

# If the directory with files doesn't exist, let's download GLORIA data.

if(dir.exists('Data/GLORIA_2022/') == FALSE) {
  
      # Download
      download.file(URL, 'Data/GLORIA.zip')
      
      # Extract
      unzip(zipfile = 'Data/GLORIA.zip', exdir = 'Data')
      
      
}

After downloading the data, we can explore it. It is composed of various sheets, such as:

  1. Remote sensing reflectance
  2. Water quality variables and other auxiliary data
  3. Radiometric quantities (e.g., water leaving radiance, sky radiance)
  4. Quality control flags (e.g., suspected spectra are flagged and could be removed by the user)
  5. Uncertantity table (mean and standard deviation of provided data)

We reccomend the user to open the table in Excel or Google Docs and explore it. In this exercise, we gonna use the following tables:

  1. GLORIA_meta_and_lab.csv
  2. GLORIA_Rrs.csv

Let’s remember that Remote Sensing Reflectance is the ratio between water leaving radiance and downwelling irradiance, compensated by the sky radiance and corrected by glint effects (Equation 01).

\[\begin{equation} R_{rs} = (L_t - (L_{sky}*\rho))/E_s \end{equation}\]

These tables contain all the necessary variables we need to produce the desired algorithms. Let`s now open and explore it. You gonna see that the collumn GLORIA_ID contains the ID of each unique station. This collumn will be used in the next steps to merge both tables.



## See the dataset

# META and Lab data

meta_and_lab = fread("Data/GLORIA_2022/GLORIA_meta_and_lab.csv")
head(meta_and_lab)
NA
NA

# Rrs data

rrs = fread("Data/GLORIA_2022/GLORIA_Rrs.csv")
head(rrs)
NA

Simulation of satellite bands

However, this is a hiperspectral dataset. If we want to develop RS models, we will need to simulate (i.e. integrate based on the Relative Spectral Response) each band of the desiered sensor

To do that, we gonna use the bandSimulation package available in GitHub.


devtools::install_github("dmaciel123/BandSimulation")
Skipping install of 'bandSimulation' from a github remote, the SHA1 (88ed39b8) has not changed since last install.
  Use `force = TRUE` to force installation
require(bandSimulation)

## Simulation for Sentinel-2 MSI sensor 


spectra_formated = select(rrs, paste("Rrs_", 400:900, sep = '')) %>% t()

head(spectra_formated[1:10,1:10])
               [,1]         [,2]        [,3]        [,4]        [,5]        [,6]        [,7]        [,8]        [,9]       [,10]
Rrs_400 0.001032099 0.0009919781 0.001176673 0.001045943 0.001123216 0.001262030 0.001502713 0.001159079 0.001598394 0.002235938
Rrs_401 0.001026443 0.0009886549 0.001172247 0.001044805 0.001121460 0.001260533 0.001500229 0.001153895 0.001589690 0.002231068
Rrs_402 0.001025443 0.0009907777 0.001173282 0.001049251 0.001125342 0.001264954 0.001506565 0.001155018 0.001587384 0.002237669
Rrs_403 0.001042929 0.0010105485 0.001195316 0.001072244 0.001149169 0.001291754 0.001539501 0.001176772 0.001613440 0.002284503
Rrs_404 0.001032327 0.0009995005 0.001184033 0.001062849 0.001139232 0.001281843 0.001521784 0.001162394 0.001597356 0.002260299
Rrs_405 0.001033530 0.0010029657 0.001187121 0.001068868 0.001144698 0.001288210 0.001528472 0.001164688 0.001597279 0.002268107
MSI_sim = msi_simulation(spectra = spectra_formated, 
                         point_name = rrs$GLORIA_ID)


#It simulates for Sentinel-2A and Sentinel-2B and gives the results in a list.
# Let's select only Sentinel-2A.

MSI = MSI_sim$s2a[,-1] %>% t() %>% data.frame()

head(MSI[,1:9])


# Add names to a collumn
MSI$GLORIA_ID = row.names(MSI)

#Check final 

head(MSI[,1:9])
NA
NA
NA
NA

Checking the results and understanding what is “Band Simulation”

When we simulate a satellite band, we are compensating for the differences in detector sensibility to each wavelength. The Figure below shows differences in the spectral response function for Sentinel-2A/MSI, Landsat-8/OLI and Landsat-7/ETM+. You can note that relative spectral response values close to “1” indicates that the detector can measure (or detect) all the radiance in this wavelength. A sensor band is composed by an interval of these wavelengths and then, the simulated band is the integral the R[rs] considering the Relative Spectral Response curve, or Equation 02.

Figure 02

\[\begin{equation} R_{rs} = \frac{\int_{n}^{m} SRF(\lambda) R_{rs} dx}{\int_{n}^{m} SRF(\lambda)} \end{equation}\]

# Change band names

names(MSI) = c('x440', "x490", 'x560', 'x660', "x705", 'x740', 'x780', 'x850', 'x865', "GLORIA_ID")

ROW = 150

# Example

matplot(t(select(rrs, paste("Rrs_", 400:900, sep = ''))[ROW,]), ylim = c(0,0.015),
        x= c(400:900), pch = 20, xlab = '', ylab = '')

par(new=T)

matplot(t(MSI[ROW,-10]), x= c(440,490,560,660,705,740,780,850,860), pch = 20,
        ylim = c(0,0.015), xlim = c(400,900), col = 'red', cex = 2, xlab = 'Wavelength (nm)', 
        ylab = 'Rrs (sr-1)')

NA
NA

## Merge with water quality, lat long (By GLORIA_ID)

merged = merge(select(meta_and_lab, c('GLORIA_ID', 'Chla', "TSS", "Latitude", "Longitude")),
               MSI, by = "GLORIA_ID")

head(merged)
NA

#vector = vect(merged, 
#              geom = c('Longitude', 'Latitude'), 
#              "EPSG:4326")


#mapview(sf::st_as_sf(vector),  zcol = 'TSS')

Finalizing the exploratory analyze

Before continuing and saving the simulated data, we will need to remove No-data values, perform some filter process (e.g., remove very low values of Chl-a) and also calculate some indexes that could help into modeling process.

Let’s do that.

# We want to model TSS and Chla concentration based on RF

merged = merged[is.na(merged$Chla) == FALSE, ]
merged = merged[is.na(merged$TSS) == FALSE, ]

head(merged)


## Index calculation:

merged$NDCI = (merged$x705-merged$x660)/(merged$x705+merged$x660)+1
merged$Blue_red = (merged$x490-merged$x660)/(merged$x490+merged$x660)+1
merged$green_blue = (merged$x560-merged$x490)/(merged$x560+merged$x490)+1
merged$nir_red = (merged$x740-merged$x660)/(merged$x740+merged$x660)+1


# Check dimension

dim(merged)
[1] 3203   18

Performing some exploratory analyzes.


chart.Correlation(merged[,-1])

NA
NA
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpgYGB7cn0KCmBgYAoKIyBXYXRlciBxdWFsaXR5IHBhcmFtZXRlcnMgcmV0cmlldmFsIHVzaW5nIFJlbW90ZSBTZW5zaW5nIGRhdGEgLSBQYXJ0IDAxCgoKVGhlIHVzZSBvZiByZW1vdGUgc2Vuc2luZyBkYXRhIHRvIHByZWRpY3Qgd2F0ZXIgcXVhbGl0eSB2YXJpYWJsZXMgdGhhdCBhcmUgb3B0aWNhbGx5IGFjdGl2ZSAoaS5lLiwgaW50ZXJhY3RzIHdpdGggbGlnaHQpIGhhcyBiZWVuIGFwcGxpZWQgdG8gb2NlYW4gYW5kIGNvYXN0YWwgd2F0ZXJzIGZvciB+NTAgeWVhcnMuIFRoYW5rcyB0byB0aGUgbmV3IGdlbmVyYXRpb24gb2Ygc2Vuc29ycyB3aXRoIGFkZXF1YXRlIHNwZWN0cmFsLCByYWRpb21ldHJpYyBhbmQgc3BhdGlhbCByZXNvbHV0aW9uIChpLmUuLCBMYW5kc2F0LCBTZW50aW5lbC0yLCBldGMpIGluIHRoZSBsYXN0IDE1IHllYXJzIHRoZSBjb21tdW5pdHkgc3RhcnRlZCB0byB1c2UgUlMgdG8gZnJlc2h3YXRlciBzdHVkaWVzLiAKCgpSZW1vdGUgc2Vuc2luZyBhbGxvdyB1cyB0byBwcmVkaWN0IHNvbWUgV1EgcGFyYW1ldGVyczogU3VzcGVuZGVkIHNlZGltZW50cywgY2hsb3JvcGh5bC1hLCBwaHljb2NpYW5pbiwgZGlzc29sdmVkIG9yZ2FuaWMgbWF0dGVyLCBjYXJib24sIHNlY2NoaSBkaXNrIGRlcHRoLCB0dXJiaWRpdHkuLi5JdCBpcyBhbiBpbXBvcnRhbnQgc291cmNlIG9mIGRhdGEgdGhhdCBjb3VsZCBoZWxwIGJpb2xvZ2lzdHMsIGxpbW5vbG9naXN0cywgYW5kIGFsbCB0aGUgYXF1YXRpYyBzY2llbmNlIGNvbW11bml0eSBpbnRvIHVuZGVyc3RhbmRpbmcgb2Ygd2F0ZXIgcGF0dGVybi4gCgoKCkluIHRoaXMgTm90ZWJvb2ssIHdlIGdvbm5hIGxlYXJuIGhvdyB0byB1c2UgUmVtb3RlIFNlbnNpbmcgZGF0YSB0byBwcmVkaWN0IHdhdGVyIHF1YWxpdHkgdmFyaWFibGVzIChDaGwtYSBhbmQgU3VzcGVuZGVkIFNlZGltZW50cykuIEZvciB0aGF0LCB3ZSBnb25uYSB1c2UgYSBoeXBlcnNwZWN0cmFsIGRhdGFzZXQgZm9yIGdsb2JhbCBmcmVzaHdhdGVyIChHTE9SSUEpIHRvIHNpbXVsYXRlZCBMYW5kc2F0IGFuZCBTZW50aW5lbC0yIGJhbmRzIChpLmUuLCBtYWtlcyB0aGUgaW4gc2l0dSBkYXRhIHRoZSAic2FtZSIgYXMgc2F0ZWxsaXRlIHNlZSkuIEFmdGVyIHRoYXQsIHdlIGdvbm5hIGRvIHNvbWUgZXhwbG9yYXRvcnkgYW5hbHlzaXMgYW5kIGZpbHRlcnMgdG8gcmVtb3ZlIG91dGxpZXJzIGFuZCBkZXZlbG9wIGEgZW1waXJpY2FsIGFuZCBNYWNoaW5lIExlYXJuaW5nIGFsZ29yaXRobSAoUmFuZG9tIEZvcmVzdCkgdG8gcHJlZGljdCBDaGwtYSBhbmQgVFNTIGZvciBzYXRlbGxpdGUgaW1hZ2VzLiAKClRoZSBwcm9jZXNzaW5nIGZsb3cgaXMgZGl2aWRlZCBpbnRvIHRocmVlIHRvcGljczoKCjEuIEluc3RhbGxpbmcgdGhlIHBhY2thZ2VzLCBkb3dubG9hZGluZyB0aGUgZGF0YSwgc2ltdWxhdGUgdGhlIGJhbmRzIGFuZCByZW1vdmluZyBvdXRsaWVycyAocHJlLXByb2Nlc3Npbmcgc3RlcCkKMi4gTW9kZWwgZGV2ZWxvcG1lbnQgKHRyYWluLCB2YWxpZGF0aW9uLCBhbmQgZnVsbCBtb2RlbCBkZXZlbG9wbWVudCkKMy4gTW9kZWwgYXBwbGljYXRpb246IGFwcGxpY2F0aW9uIG9mIHRoZSBhbGdvcml0aG1zIHRvIHNhdGVsbGl0ZSBkYXRhIHVzaW5nIFNUQUMgYW5kIEdvb2dsZSBFYXJ0aCBFbmdpbmUgCgoKCmBgYHtyfQoKI2luc3RhbGwucGFja2FnZXMoYygnZGF0YS50YWJsZScsCiMgICAgICAgICAgICAgICAgICAgICAgICAnZHBseXInLAojICAgICAgICAgICAgICAgICAgICAgICAgJ3JzdGFjJywKIyAgICAgICAgICAgICAgICAgICAgICAgICd0ZXJyYScsCiMgICAgICAgICAgICAgICAgICAgICAgICAncmFzdGVyJywKIyAgICAgICAgICAgICAgICAgICAgICAgICdtYXB2aWV3JywKIyAgICAgICAgICAgICAgICAgICAgICAgICdNZXRyaWNzJywKIyAgICAgICAgICAgICAgICAgICAgICAgICdnZW9kcmF3cicsCiMgICAgICAgICAgICAgICAgICAgICAgICAnc3ZEaWFsb2dzJywKIyAgICAgICAgICAgICAgICAgICAgICAgICAnUGVyZm9ybWFuY2VBbmFseXRpY3MnLAojICAgICAgICAgICAgICAgICAgICAgICAgJ3JzdGFjJywKIyAgICAgICAgICAgICAgICAgICAgICAgICd3ZXNhbmRlcnNvbicpKQoKCnJlcXVpcmUoZGF0YS50YWJsZSkKcmVxdWlyZShkcGx5cikKcmVxdWlyZShyc3RhYykKcmVxdWlyZSh0ZXJyYSkKcmVxdWlyZShtYXB2aWV3KQpyZXF1aXJlKGh0dHIpCnJlcXVpcmUoTWV0cmljcykKcmVxdWlyZShnZW9kcmF3cikKcmVxdWlyZShzdkRpYWxvZ3MpCnJlcXVpcmUocnN0YWMpCnJlcXVpcmUod2VzYW5kZXJzb24pCnJlcXVpcmUoUGVyZm9ybWFuY2VBbmFseXRpY3MpCgoKCmBgYAojIERvd25sb2FkaW5nIEdMT1JJQSBkYXRhCgpUaGUgR0xPUklBIGRhdGFzZXQgaXMgYSBjb21waWxhdGlvbiBvZiByZW1vdGUgc2Vuc2luZyBhbmQgd2F0ZXIgcXVhbGl0eSBkYXRhIGZvciBnbG9iYWwgd2F0ZXJzLCB3aXRoIGRlZGljYXRlZCBkYXRhIGZvciBmcmVzaHdhdGVyIGVjb3N5c3RlbXMuIEl0IGlzIGZyZWUgYW5kIGF2YWlsYWJsZSBmb3IgZXZlcnlvbmUsIGFuZCBjb3ZlcnMgbW9zdCBwYXJ0IG9mIHRoZSBnbG9iZSB3aXRoIG1vcmUgdGhhbiA3LDAwMCBzYW1wbGVzIChGaWd1cmUgMDEpCgohW0ZpZ3VyZSAwMV0oaHR0cHM6Ly9lYXJ0aGVudmlyb25tZW50Y29tbXVuaXR5Lm5hdHVyZS5jb20vY2RuLWNnaS9pbWFnZS9tZXRhZGF0YT1jb3B5cmlnaHQsZml0PXNjYWxlLWRvd24sZm9ybWF0PWF1dG8sc2hhcnBlbj0xLHF1YWxpdHk9OTUvaHR0cHM6Ly9pbWFnZXMuemFwbml0by5jb20vdXBsb2Fkcy9oaUNNT3ByblR0U0NUSk52NzhndV9sb2NhdGlvbnMuanBnKQoKCkZvciBtb3JlIGluZm9ybWF0aW9uLCB1c2VycyBhcmUgcmVmZXJlZCB0byB0aGUgcHVibGljYXRpb24gWyhMZWhtYW5uIGV0IGFsLiAyMDIzKV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE1OTctMDIzLTAxOTczLXkpLCB0aGUgZGF0YXNldCBpbiBbUEFOR0FFQV0oaHR0cDovL2h0dHBzOi8vZG9pLnBhbmdhZWEuZGUvMTAuMTU5NC9QQU5HQUVBLjk0ODQ5MikgYW5kIHRoZSBbTmF0dXJlIEVhcnRoIGFuZCBFbnZpcm9ubW1lbnQgYmxvZyBwb3N0XShodHRwOi8vaHR0cHM6Ly9lYXJ0aGVudmlyb25tZW50Y29tbXVuaXR5Lm5hdHVyZS5jb20vcG9zdHMvZ2xvcmlhLWNoYWxsZW5nZXMtaW4tZGV2ZWxvcGluZy1hLWdsb2JhbGx5LXJlcHJlc2VudGF0aXZlLWh5cGVyc3BlY3RyYWwtaW4tc2l0dS1kYXRhc2V0LWZvci10aGUtcmVtb3RlLXNlbnNpbmctb2Ytd2F0ZXItcmVzb3VyY2VzKQoKSW4gdGhlIGZvbGxvd2luZyBjb2Rlcywgd2UgYXJlIGdvbm5hOgoKMS4gQ3JlYXRlIHRoZSBuZWNlc3NhcnkgZm9sZGVycwoyLiBEb3dubG9hZCB0aGUgZGF0YSBmcm9tIFBBTkdBRUEKMy4gRXh0cmFjdCBpdCBpbiBSCgoKYGBge3J9CgojIExldCdzIGRvd25sb2FkIHRoZSBHTE9SSUEgdG8gYSBlbXB0eSBmb2xkZXIgY2FsbGVkICJEYXRhIiBpbiBvdXIgcHJvamVjdAoKIyMgRmlyc3QsIGNyZWF0ZSBkaXJlY3RvcmllcwoKZGlyLmNyZWF0ZSgiRGF0YSIpCmRpci5jcmVhdGUoIk91dHB1dHMiKQpkaXIuY3JlYXRlKCJTY3JpcHRzIikKCiMjIFVSTApVUkwgPSAnaHR0cHM6Ly9kb3dubG9hZC5wYW5nYWVhLmRlL2RhdGFzZXQvOTQ4NDkyL2ZpbGVzL0dMT1JJQS0yMDIyLnppcCcKCiMgQmVmb3JlIGRvd25sb2FkLCBsZXRgcyBzZXQgdGltZW91dCB0byAyMDBzIChzb21ldGltZXMgUEFOR0FFQSBkb3dubG9hZCBpcyBzbG93KQoKb3B0aW9ucyh0aW1lb3V0PTMwMCkKCiMgSWYgdGhlIGRpcmVjdG9yeSB3aXRoIGZpbGVzIGRvZXNuJ3QgZXhpc3QsIGxldCdzIGRvd25sb2FkIEdMT1JJQSBkYXRhLgoKaWYoZGlyLmV4aXN0cygnRGF0YS9HTE9SSUFfMjAyMi8nKSA9PSBGQUxTRSkgewogIAogICAgICAjIERvd25sb2FkCiAgICAgIGRvd25sb2FkLmZpbGUoVVJMLCAnRGF0YS9HTE9SSUEuemlwJykKICAgICAgCiAgICAgICMgRXh0cmFjdAogICAgICB1bnppcCh6aXBmaWxlID0gJ0RhdGEvR0xPUklBLnppcCcsIGV4ZGlyID0gJ0RhdGEnKQogICAgICAKICAgICAgCn0KCgoKCmBgYAoKQWZ0ZXIgZG93bmxvYWRpbmcgdGhlIGRhdGEsIHdlIGNhbiBleHBsb3JlIGl0LiBJdCBpcyBjb21wb3NlZCBvZiB2YXJpb3VzIHNoZWV0cywgc3VjaCBhczoKCjEuIFJlbW90ZSBzZW5zaW5nIHJlZmxlY3RhbmNlIAoyLiBXYXRlciBxdWFsaXR5IHZhcmlhYmxlcyBhbmQgb3RoZXIgYXV4aWxpYXJ5IGRhdGEKMy4gUmFkaW9tZXRyaWMgcXVhbnRpdGllcyAoZS5nLiwgd2F0ZXIgbGVhdmluZyByYWRpYW5jZSwgc2t5IHJhZGlhbmNlKQo0LiBRdWFsaXR5IGNvbnRyb2wgZmxhZ3MgKGUuZy4sIHN1c3BlY3RlZCBzcGVjdHJhIGFyZSBmbGFnZ2VkIGFuZCBjb3VsZCBiZSByZW1vdmVkIGJ5IHRoZSB1c2VyKQo1LiBVbmNlcnRhbnRpdHkgdGFibGUgKG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBwcm92aWRlZCBkYXRhKQoKV2UgcmVjY29tZW5kIHRoZSB1c2VyIHRvIG9wZW4gdGhlIHRhYmxlIGluIEV4Y2VsIG9yIEdvb2dsZSBEb2NzIGFuZCBleHBsb3JlIGl0LiBJbiB0aGlzIGV4ZXJjaXNlLCB3ZSBnb25uYSB1c2UgdGhlIGZvbGxvd2luZyB0YWJsZXM6CgoxLiBHTE9SSUFfbWV0YV9hbmRfbGFiLmNzdgoyLiBHTE9SSUFfUnJzLmNzdgoKTGV0J3MgcmVtZW1iZXIgdGhhdCBSZW1vdGUgU2Vuc2luZyBSZWZsZWN0YW5jZSBpcyB0aGUgcmF0aW8gYmV0d2VlbiB3YXRlciBsZWF2aW5nIHJhZGlhbmNlIGFuZCBkb3dud2VsbGluZyBpcnJhZGlhbmNlLCBjb21wZW5zYXRlZCBieSB0aGUgc2t5IHJhZGlhbmNlIGFuZCBjb3JyZWN0ZWQgYnkgZ2xpbnQgZWZmZWN0cyAoRXF1YXRpb24gMDEpLgoKClxiZWdpbntlcXVhdGlvbn0KUl97cnN9ID0gKExfdCAtIChMX3tza3l9KlxyaG8pKS9FX3MKXGVuZHtlcXVhdGlvbn0KCgpUaGVzZSB0YWJsZXMgY29udGFpbiBhbGwgdGhlIG5lY2Vzc2FyeSB2YXJpYWJsZXMgd2UgbmVlZCB0byBwcm9kdWNlIHRoZSBkZXNpcmVkIGFsZ29yaXRobXMuIExldGBzIG5vdyBvcGVuIGFuZCBleHBsb3JlIGl0LiBZb3UgZ29ubmEgc2VlIHRoYXQgdGhlIGNvbGx1bW4gR0xPUklBX0lEIGNvbnRhaW5zIHRoZSBJRCBvZiBlYWNoIHVuaXF1ZSBzdGF0aW9uLiBUaGlzIGNvbGx1bW4gd2lsbCBiZSB1c2VkIGluIHRoZSBuZXh0IHN0ZXBzIHRvIG1lcmdlIGJvdGggdGFibGVzLgoKYGBge3J9CgoKIyMgU2VlIHRoZSBkYXRhc2V0CgojIE1FVEEgYW5kIExhYiBkYXRhCgptZXRhX2FuZF9sYWIgPSBmcmVhZCgiRGF0YS9HTE9SSUFfMjAyMi9HTE9SSUFfbWV0YV9hbmRfbGFiLmNzdiIpCmhlYWQobWV0YV9hbmRfbGFiKQoKCmBgYAoKYGBge3J9CgojIFJycyBkYXRhCgpycnMgPSBmcmVhZCgiRGF0YS9HTE9SSUFfMjAyMi9HTE9SSUFfUnJzLmNzdiIpCmhlYWQocnJzKQoKYGBgCgojIFNpbXVsYXRpb24gb2Ygc2F0ZWxsaXRlIGJhbmRzCgoKSG93ZXZlciwgdGhpcyBpcyBhIGhpcGVyc3BlY3RyYWwgZGF0YXNldC4gSWYgd2Ugd2FudCB0byBkZXZlbG9wIFJTIG1vZGVscywgd2Ugd2lsbCBuZWVkIHRvIHNpbXVsYXRlIChpLmUuIGludGVncmF0ZSBiYXNlZCBvbiB0aGUgUmVsYXRpdmUgU3BlY3RyYWwgUmVzcG9uc2UpIGVhY2ggYmFuZCBvZiB0aGUgZGVzaWVyZWQgc2Vuc29yCgpUbyBkbyB0aGF0LCB3ZSBnb25uYSB1c2UgdGhlIGJhbmRTaW11bGF0aW9uIHBhY2thZ2UgYXZhaWxhYmxlIGluIEdpdEh1Yi4gCgpgYGB7cn0KCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiZG1hY2llbDEyMy9CYW5kU2ltdWxhdGlvbiIpCgpyZXF1aXJlKGJhbmRTaW11bGF0aW9uKQoKYGBgCgpgYGB7cn0KCiMjIFNpbXVsYXRpb24gZm9yIFNlbnRpbmVsLTIgTVNJIHNlbnNvciAKCgpzcGVjdHJhX2Zvcm1hdGVkID0gc2VsZWN0KHJycywgcGFzdGUoIlJyc18iLCA0MDA6OTAwLCBzZXAgPSAnJykpICU+JSB0KCkKCmhlYWQoc3BlY3RyYV9mb3JtYXRlZFsxOjEwLDE6MTBdKQoKTVNJX3NpbSA9IG1zaV9zaW11bGF0aW9uKHNwZWN0cmEgPSBzcGVjdHJhX2Zvcm1hdGVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50X25hbWUgPSBycnMkR0xPUklBX0lEKQoKCiNJdCBzaW11bGF0ZXMgZm9yIFNlbnRpbmVsLTJBIGFuZCBTZW50aW5lbC0yQiBhbmQgZ2l2ZXMgdGhlIHJlc3VsdHMgaW4gYSBsaXN0LgojIExldCdzIHNlbGVjdCBvbmx5IFNlbnRpbmVsLTJBLgoKTVNJID0gTVNJX3NpbSRzMmFbLC0xXSAlPiUgdCgpICU+JSBkYXRhLmZyYW1lKCkKCmhlYWQoTVNJWywxOjldKQoKCiMgQWRkIG5hbWVzIHRvIGEgY29sbHVtbgpNU0kkR0xPUklBX0lEID0gcm93Lm5hbWVzKE1TSSkKCiNDaGVjayBmaW5hbCAKCiNoZWFkKE1TSVssMTo5XSkKCgoKCmBgYAoKIyBDaGVja2luZyB0aGUgcmVzdWx0cyBhbmQgdW5kZXJzdGFuZGluZyB3aGF0IGlzICJCYW5kIFNpbXVsYXRpb24iIAoKV2hlbiB3ZSBzaW11bGF0ZSBhIHNhdGVsbGl0ZSBiYW5kLCB3ZSBhcmUgY29tcGVuc2F0aW5nIGZvciB0aGUgZGlmZmVyZW5jZXMgaW4gZGV0ZWN0b3Igc2Vuc2liaWxpdHkgdG8gZWFjaCB3YXZlbGVuZ3RoLiBUaGUgRmlndXJlIGJlbG93IHNob3dzIGRpZmZlcmVuY2VzIGluIHRoZSBzcGVjdHJhbCByZXNwb25zZSBmdW5jdGlvbiBmb3IgU2VudGluZWwtMkEvTVNJLCBMYW5kc2F0LTgvT0xJIGFuZCBMYW5kc2F0LTcvRVRNKy4gWW91IGNhbiBub3RlIHRoYXQgcmVsYXRpdmUgc3BlY3RyYWwgcmVzcG9uc2UgdmFsdWVzIGNsb3NlIHRvICIxIiBpbmRpY2F0ZXMgdGhhdCB0aGUgZGV0ZWN0b3IgY2FuIG1lYXN1cmUgKG9yIGRldGVjdCkgYWxsIHRoZSByYWRpYW5jZSBpbiB0aGlzIHdhdmVsZW5ndGguIEEgc2Vuc29yIGJhbmQgaXMgY29tcG9zZWQgYnkgYW4gaW50ZXJ2YWwgb2YgdGhlc2Ugd2F2ZWxlbmd0aHMgYW5kIHRoZW4sIHRoZSBzaW11bGF0ZWQgYmFuZCBpcyB0aGUgaW50ZWdyYWwgdGhlIFJbcnNdIGNvbnNpZGVyaW5nIHRoZSBSZWxhdGl2ZSBTcGVjdHJhbCBSZXNwb25zZSBjdXJ2ZSwgb3IgRXF1YXRpb24gMDIuCgoKIVtGaWd1cmUgMDJdKGh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvNy83ZC9TcGVjdHJhbF9yZXNwb25zZXNfb2ZfTGFuZHNhdF83X0VUTSUyQiUyQ19MYW5kc2F0XzhfT0xJX2FuZF9TZW50aW5lbF8yX01TSV9pbl90aGVfdmlzaWJsZV9hbmRfbmVhcl9pbmZyYXJlZC5wbmcpCgpcYmVnaW57ZXF1YXRpb259ClJfe3JzfSA9ICBcZnJhY3tcaW50X3tufV57bX0gU1JGKFxsYW1iZGEpIFJfe3JzfSBkeH17XGludF97bn1ee219IFNSRihcbGFtYmRhKX0gClxlbmR7ZXF1YXRpb259CgpgYGB7cn0KIyBDaGFuZ2UgYmFuZCBuYW1lcwoKbmFtZXMoTVNJKSA9IGMoJ3g0NDAnLCAieDQ5MCIsICd4NTYwJywgJ3g2NjAnLCAieDcwNSIsICd4NzQwJywgJ3g3ODAnLCAneDg1MCcsICd4ODY1JywgIkdMT1JJQV9JRCIpCgpST1cgPSAxNTAKCiMgRXhhbXBsZQoKbWF0cGxvdCh0KHNlbGVjdChycnMsIHBhc3RlKCJScnNfIiwgNDAwOjkwMCwgc2VwID0gJycpKVtST1csXSksIHlsaW0gPSBjKDAsMC4wMTUpLAogICAgICAgIHg9IGMoNDAwOjkwMCksIHBjaCA9IDIwLCB4bGFiID0gJycsIHlsYWIgPSAnJykKCnBhcihuZXc9VCkKCm1hdHBsb3QodChNU0lbUk9XLC0xMF0pLCB4PSBjKDQ0MCw0OTAsNTYwLDY2MCw3MDUsNzQwLDc4MCw4NTAsODYwKSwgcGNoID0gMjAsCiAgICAgICAgeWxpbSA9IGMoMCwwLjAxNSksIHhsaW0gPSBjKDQwMCw5MDApLCBjb2wgPSAncmVkJywgY2V4ID0gMiwgeGxhYiA9ICdXYXZlbGVuZ3RoIChubSknLCAKICAgICAgICB5bGFiID0gJ1JycyAoc3ItMSknKQoKCmBgYAoKCgpgYGB7cn0KCiMjIE1lcmdlIHdpdGggd2F0ZXIgcXVhbGl0eSwgbGF0IGxvbmcgKEJ5IEdMT1JJQV9JRCkKCm1lcmdlZCA9IG1lcmdlKHNlbGVjdChtZXRhX2FuZF9sYWIsIGMoJ0dMT1JJQV9JRCcsICdDaGxhJywgIlRTUyIsICJMYXRpdHVkZSIsICJMb25naXR1ZGUiKSksCiAgICAgICAgICAgICAgIE1TSSwgYnkgPSAiR0xPUklBX0lEIikKCmhlYWQobWVyZ2VkKQoKYGBgCgoKYGBge3J9CgojdmVjdG9yID0gdmVjdChtZXJnZWQsIAojICAgICAgICAgICAgICBnZW9tID0gYygnTG9uZ2l0dWRlJywgJ0xhdGl0dWRlJyksIAojICAgICAgICAgICAgICAiRVBTRzo0MzI2IikKCgojbWFwdmlldyhzZjo6c3RfYXNfc2YodmVjdG9yKSwgIHpjb2wgPSAnVFNTJykKCmBgYAoKIyBGaW5hbGl6aW5nIHRoZSBleHBsb3JhdG9yeSBhbmFseXplCgpCZWZvcmUgY29udGludWluZyBhbmQgc2F2aW5nIHRoZSBzaW11bGF0ZWQgZGF0YSwgd2Ugd2lsbCBuZWVkIHRvIHJlbW92ZSBOby1kYXRhIHZhbHVlcywgcGVyZm9ybSBzb21lIGZpbHRlciBwcm9jZXNzIChlLmcuLCByZW1vdmUgdmVyeSBsb3cgdmFsdWVzIG9mIENobC1hKSBhbmQgYWxzbyBjYWxjdWxhdGUgc29tZSBpbmRleGVzIHRoYXQgY291bGQgaGVscCBpbnRvIG1vZGVsaW5nIHByb2Nlc3MuIAoKTGV0J3MgZG8gdGhhdC4KCgpgYGB7cn0KCiMgV2Ugd2FudCB0byBtb2RlbCBUU1MgYW5kIENobGEgY29uY2VudHJhdGlvbiBiYXNlZCBvbiBSRgoKbWVyZ2VkID0gbWVyZ2VkW2lzLm5hKG1lcmdlZCRDaGxhKSA9PSBGQUxTRSwgXQptZXJnZWQgPSBtZXJnZWRbaXMubmEobWVyZ2VkJFRTUykgPT0gRkFMU0UsIF0KCiNoZWFkKG1lcmdlZCkKCgojIyBJbmRleCBjYWxjdWxhdGlvbjoKCm1lcmdlZCRORENJID0gKG1lcmdlZCR4NzA1LW1lcmdlZCR4NjYwKS8obWVyZ2VkJHg3MDUrbWVyZ2VkJHg2NjApKzEKbWVyZ2VkJEJsdWVfcmVkID0gKG1lcmdlZCR4NDkwLW1lcmdlZCR4NjYwKS8obWVyZ2VkJHg0OTArbWVyZ2VkJHg2NjApKzEKbWVyZ2VkJGdyZWVuX2JsdWUgPSAobWVyZ2VkJHg1NjAtbWVyZ2VkJHg0OTApLyhtZXJnZWQkeDU2MCttZXJnZWQkeDQ5MCkrMQptZXJnZWQkbmlyX3JlZCA9IChtZXJnZWQkeDc0MC1tZXJnZWQkeDY2MCkvKG1lcmdlZCR4NzQwK21lcmdlZCR4NjYwKSsxCgoKIyBDaGVjayBkaW1lbnNpb24KCmRpbShtZXJnZWQpCgpgYGAKClBlcmZvcm1pbmcgc29tZSBleHBsb3JhdG9yeSBhbmFseXplcy4KCgpgYGB7cn0KCmNoYXJ0LkNvcnJlbGF0aW9uKG1lcmdlZFssLTFdKQoKCmBgYAoK